#include "filemap.h"

Napi::Object FileMap::Init(Napi::Env env, Napi::Object exports) {
  Napi::Function func =
      DefineClass(env,
                  "FileMap",
                  {
                   InstanceMethod("create", &FileMap::create),
                   InstanceMethod("open", &FileMap::open),
                   InstanceMethod("close", &FileMap::close)
                   });

  Napi::FunctionReference* constructor = new Napi::FunctionReference();
  *constructor = Napi::Persistent(func);
  env.SetInstanceData(constructor);

  exports.Set("FileMap", func);
  return exports;
}

FileMap::FileMap(const Napi::CallbackInfo& info) : Napi::ObjectWrap<FileMap>(info) {
  Napi::Env env = info.Env();

}

 FileMap::~FileMap()
 {
   this->closeMapHandle();   
 }

Napi::Value FileMap::close(const Napi::CallbackInfo& info){
    
   this->closeMapHandle();
   return Napi::Value::Value();
}

void FileMap::closeMapHandle(){
    
  if(this->_buffer != nullptr) {
     this->_buffer = nullptr;
     _tprintf(TEXT("unmap view of file\n"));
     UnmapViewOfFile(this->_buffer);
  }

  if(this->_MapFile != nullptr)
  {
      this->_MapFile = nullptr;
       _tprintf(TEXT("close map handler\n"));
      CloseHandle(this->_MapFile);
  }

}

Napi::Value FileMap::create(const Napi::CallbackInfo& info)
{
   Napi::Value nullValue = Napi::Value::Value();
   Napi::Env env = info.Env();
   
  int length = info.Length();
  const int argumentLength = 2;
  if (length != argumentLength )
  {
    _tprintf(TEXT("arguments mismatch, should be %d\n"),argumentLength);
    Napi::TypeError::New(env, "argument mismatch").ThrowAsJavaScriptException();    
    return nullValue;
  }

  if(!info[0].IsString()) {
    Napi::TypeError::New(env, "invalid file mapping name").ThrowAsJavaScriptException();
    return nullValue;
  }

  if(!info[1].IsNumber())
  {
    Napi::TypeError::New(env, "invalid buffer size").ThrowAsJavaScriptException();
    return nullValue;    
  }

   LPCTSTR pBuf;
   std::string mappingName = info[0].As<Napi::String>();
   const char* mapName = mappingName.c_str();
   int bufSize = info[1].As<Napi::Number>().Int32Value();

   _tprintf(TEXT("create file map with name=%s,buffer size=%d\n"),mapName,bufSize);

    this->_MapFile = CreateFileMapping(
                 INVALID_HANDLE_VALUE,    // use paging file
                 NULL,                    // default security
                 PAGE_READWRITE,          // read/write access
                 0,                       // maximum object size (high-order DWORD)
                 bufSize,                // maximum object size (low-order DWORD)
                 mapName);                 // name of mapping object

   if (this->_MapFile == NULL)
   {
      _tprintf(TEXT("create file mapping object failed.(error=%d).\n"),
             GetLastError());
      return nullValue;
   }
   
   _tprintf(TEXT("map view of file\n"));

   pBuf = (LPTSTR) MapViewOfFile(this->_MapFile,   // handle to map object
                        FILE_MAP_ALL_ACCESS, // read/write permission
                        0,
                        0,
                        bufSize);

   if (pBuf == NULL)
   {
      _tprintf(TEXT("map view of file failed.(error=%d).\n"),GetLastError());

      this->closeMapHandle();
      return nullValue;
   }
   
   this->_buffer = pBuf;
   return Napi::Buffer<char>::New(env,(char*)pBuf, bufSize);
}


Napi::Value FileMap::open(const Napi::CallbackInfo& info)
{
   Napi::Value nullValue = Napi::Value::Value();
   Napi::Env env = info.Env();
   int length = info.Length();
   const int argumentLength = 3;
   if (length != argumentLength )
   {
     _tprintf(TEXT("arguments mismatch, should be %d\n"),argumentLength);
     Napi::TypeError::New(env, "argument mismatch").ThrowAsJavaScriptException();    
     return nullValue;
   }

  if(!info[0].IsString()) {
    Napi::TypeError::New(env, "invalid file map name").ThrowAsJavaScriptException();
    return nullValue;
  }

  if(!info[1].IsNumber())
  {
    Napi::TypeError::New(env, "invalid buffer size").ThrowAsJavaScriptException();
    return nullValue;    
  }
   
   if(!info[2].IsNumber())
  {
    Napi::TypeError::New(env, "invalid buffer byte offset").ThrowAsJavaScriptException();
    return nullValue;    
  }

   std::string mappingName = info[0].As<Napi::String>();
   const char* mapName = mappingName.c_str();
   int bufSize = info[1].As<Napi::Number>().Int32Value();
   uint64_t offset = info[2].As<Napi::Number>().Int64Value();
   
   
   if(this->_MapFile != NULL)
    {
       _tprintf(TEXT("file mapping is already opened.\n"));
       return nullValue;
    }
   
   _tprintf(TEXT("open file mapping with name=%s\n"),mapName);
   this->_MapFile = OpenFileMapping(
                   FILE_MAP_READ,   // read/write access
                   FALSE,                 // do not inherit the name
                   mapName);               // name of mapping object
   
   if (this->_MapFile == NULL)
   {
        _tprintf(TEXT("open file mapping object failed.(error=%d).\n"),
             GetLastError());
        return nullValue;
   }
    
  
    uint32_t lowOffset = (uint32_t) offset;
    uint32_t highOffset = uint32_t(offset >> 32);
    _tprintf(TEXT("Map View of File with buffer size=%d, offset high=%d,offset low=%d.\n"),bufSize,highOffset,lowOffset); 
   const char* buffer = (LPTSTR) MapViewOfFile(this->_MapFile, // handle to map object
               FILE_MAP_READ,  // read permission
               highOffset,
               lowOffset,
               bufSize);

   if (buffer == NULL)
   {
      _tprintf(TEXT("map view of file failed. (error=%d).\n"),
             GetLastError());

      this->closeMapHandle();
      return nullValue;
   }

   this->_buffer = buffer;

   return Napi::Buffer<char>::New(env,(char*)buffer, bufSize);
}